package client

import (
	"errors"
	"fmt"
	"iot-base/common/fsm"
	"iot-base/common/tunnel/morrior"
	"iot-base/common/tunnel/protocol"
	"net"
	"sync"
	"time"

	//"golang.org/x/crypto/pbkdf2"

	"github.com/xtaci/kcp-go/v5"
)

func sendWakeup(pConn *net.UDPConn, buf []byte, sendBuf []byte, pInstance *fsm.FSMInstance) error {
	svrAddr, _ := pInstance.GetContext().GetString("serverAddr")
	//cport, _ := pInstance.GetContext().GetInt("cport")
	fmt.Println("-------------------", svrAddr)
	pCAddr, _ := net.ResolveUDPAddr("udp4", svrAddr)
	for i := 0; i < 10; i++ {
		_, err := pConn.WriteToUDP(sendBuf, pCAddr)
		fmt.Println("writetoudp to server", err, pCAddr)
		if err != nil {

			return err
		}
		pInstance.GetContext().Set("localAddr", pConn.LocalAddr().String())
		count := 0
		var buffr [200]byte
		for {
			if count > 5 {
				fmt.Println("retry to many times", count)
				pInstance.NotifyEvent("recvTunnelFailed")
				return errors.New("try to many times-----------")
			}
			count++

			pConn.SetReadDeadline(time.Now().Add(time.Duration(10) * time.Second))
			nlen, edgeAddr, err := pConn.ReadFromUDP(buffr[:])
			fmt.Println("count---recv----", count, nlen, err, edgeAddr)
			if err == nil && nlen > 0 {
				//pInstance.GetContext().Set("targetAddr", pAddr)
				pHeader, _ := protocol.GetHeader(buffr[:nlen], nlen)
				fmt.Println("header---type:----", pHeader.Ptype)
				if pHeader != nil {
					switch pHeader.Ptype {
					case protocol.BEGIN:
						seq, _ := pInstance.GetContext().Get("seq")
						useq := seq.(uint32)
						if pHeader.Seq == useq {
							pInstance.GetContext().Set("targetAddr", string(pHeader.Body))
							fmt.Println("----targetAddr:", string(pHeader.Body))
							pInstance.NotifyEvent("recvTunnelSucc")
							return nil
						} else {
							fmt.Println("give up for seq------------", pHeader.Seq, useq)
						}
					case protocol.PROBE, protocol.WAKEUP_RES:
						if len(pHeader.Body) > 0 {
							sid := string(pHeader.Body)
							tid, _ := pInstance.GetContext().GetString("targetID")
							if sid == tid {
								saddr := fmt.Sprintf("%s:%d", edgeAddr.IP.String(), edgeAddr.Port)
								pInstance.GetContext().Set("targetAddr", saddr)
								fmt.Println("-2---targetAddr:", edgeAddr, saddr)
								pInstance.NotifyEvent("recvTunnelSucc")
								return nil
							} else {
								fmt.Println("give up for tid------------", sid, tid)
							}
						}
					}
				}
			}
		}

	}
	pConn.Close()
	fmt.Println("timeout---------sending recvTunnelFailed")
	pInstance.NotifyEvent("recvTunnelFailed")
	return nil
}
func requestTunnel(pInstance *fsm.FSMInstance) error {
	fmt.Println("begin request tunnel------------")
	conn, _ := pInstance.GetContext().Get("conn")

	if conn != nil {
		pConn, ok := conn.(*net.UDPConn)
		if ok {
			pHeader := &protocol.Header{}
			pHeader.Ptype = protocol.WAKEUP
			pHeader.MakeSeq()
			pInstance.GetContext().Set("seq", pHeader.Seq)
			tid, _ := pInstance.GetContext().GetString("targetID")
			pHeader.Body = []byte(tid)
			var buf [100]byte
			nLen, _ := pHeader.ToBuffer(buf[:])
			go sendWakeup(pConn, buf[:], buf[0:nLen], pInstance)

		}
	}
	return nil
}

func sendProbeToEdge(pInstance *fsm.FSMInstance) error {

	fmt.Println("begin sendProbeToEdge------------")
	conn, _ := pInstance.GetContext().Get("conn")

	if conn != nil {
		pConn, ok := conn.(*net.UDPConn)
		if ok {
			var buf [100]byte
			time.Sleep(time.Duration(100) * time.Millisecond)
			pTargetAddr, _ := pInstance.GetContext().GetString("targetAddr")
			udpAddr, _ := net.ResolveUDPAddr("udp4", pTargetAddr)
			sourcePort := udpAddr.Port
			seq, _ := pInstance.GetContext().Get("seq")
			tid, _ := pInstance.GetContext().GetString("targetID")
			for i := -10; i < 5; i++ {

				pHeader := &protocol.Header{}
				pHeader.Ptype = protocol.PROBE

				pHeader.Seq, _ = seq.(uint32)

				pHeader.Body = []byte(tid)
				blen, _ := pHeader.ToBuffer(buf[:])

				pConn.WriteToUDP(buf[0:blen], udpAddr)
				//fmt.Println("probe----------- ", udpAddr)
				udpAddr.Port = sourcePort + i
				fmt.Println("-----send to --probe--", seq, udpAddr)
			}
			for {
				pConn.SetReadDeadline(time.Now().Add(time.Duration(10) * time.Second))
				nlen, pAddr, err := pConn.ReadFromUDP(buf[:])
				if err == nil && nlen > 0 {
					pHeaderRecv, _ := protocol.GetHeader(buf[0:nlen], nlen)

					if pHeaderRecv != nil {

						//targetID, _ := pInstance.GetContext().GetString("targetID")

						if (pHeaderRecv.Ptype == protocol.PROBE || pHeaderRecv.Ptype == protocol.BEGIN_KCP) && pHeaderRecv.Seq == seq {
							sourceID := string(pHeaderRecv.Body)
							if tid == sourceID {
								fmt.Println("-----get probe from----", pAddr)
								pInstance.NotifyEvent("tunnelSucc")
								return nil
							}

						} else {
							fmt.Println("data comming but ", pHeaderRecv.Ptype, protocol.PROBE,
								protocol.BEGIN_KCP, pHeaderRecv.Seq, seq, pAddr)
						}

					}
				} else {
					fmt.Println("recv error", err)
					break
				}
			}

		}
	}
	fmt.Println("2-------sendProbeToEdge---error-----")
	pInstance.NotifyEvent("tunnelFailed")
	return nil

}

func beginKCP(pInstance *fsm.FSMInstance) error {
	//server

	conn, _ := pInstance.GetContext().Get("conn")
	if conn != nil {

		//key := pbkdf2.Key([]byte("demo pass"), []byte("demo salt"), 1024, 32, sha1.New)
		//block, _ := kcp.NewAESBlockCrypt(key)
		pConn, ok := conn.(*net.UDPConn)

		if ok && pConn != nil {
			addr := pConn.LocalAddr().String()
			pConn.Close()
			if svr, err := kcp.ListenWithOptions(addr, nil, 10, 3); err == nil {
				//if svr, err := kcp.ServeConn(nil, 10, 3, pConn); err == nil {
				fmt.Println("begin listen ok", pConn.LocalAddr())
				pConn.SetReadDeadline(time.Now().Add(60 * time.Hour))
				s, err := svr.AcceptKCP()
				if err != nil {
					fmt.Println("connection recv-------err----------", err)
					return err
				}
				fmt.Println("connection recv-------succ----------")
				var wgreal *sync.WaitGroup
				wgp, _ := pInstance.GetContext().Get("wg")
				if wgp != nil {
					wgreal, _ = wgp.(*sync.WaitGroup)
				}
				fmt.Println("begin client------------------")
				pConnectionFSM := morrior.BeginClient(wgreal, s)
				if pConnectionFSM != nil {
					fmt.Println("***************--set connectionFSM---------------", pConnectionFSM)
					pConnectionFSM.GetContext().Set("server", pConn)
					pInstance.GetContext().Set("connectionFSM", pConnectionFSM)
				}
			}
		}
	}

	return nil
}
